iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
AI Engineering

30 天拆解 Wearable × AI:從穿戴裝置生理訊號到AI健康洞察系列 第 5

Day 05|每晚一個 HRV 數字是怎麼來的?從 5 分鐘視窗到 nightly metric

  • 分享至 

  • xImage
  •  

今天為什麼研究這個?

Day 4 提過,手錶與戒指拿到的是 PPG 的脈波間期,身體一動,它和 ECG 量到的 HRV 就對不太上。所以多數廠商選在睡眠時算:睡眠時身體最平穩、干擾最少,PPG 算出來的數字才夠接近 ECG 的 HRV。

但「在睡眠時算」只回答了一半。一整夜有兩三萬個心跳間距,Day 4 算的是 5 分鐘一段的 RMSSD,一夜切下來有近百段,app 早上卻只給一個數。從一夜的心跳間距到那一個數,中間還要決定:取睡眠的哪一段、視窗切多長、最後怎麼把近百個視窗壓成一個數。RHR 也一樣,字面上是清醒靜止時的心率,有些廠商卻取夜間的值;Apple 的 HRV 甚至不在睡眠時量。

今天先整理各家的公開說明,再用一個合成的一夜,把幾種壓法實際算一遍。

Concept

早上 app 給的一個 HRV、一個 RHR,不是某個時刻量到的讀數,是一串選擇的結果:取哪段時間、切多長的視窗、每段算什麼、最後怎麼把一整夜壓成一個數。各家廠商的選擇都不一樣。

1. 從一夜的心跳間期到一個數

  1. 選時段。 多數廠商取睡眠期間,但「睡眠」本身是裝置從動作與心率推估出來的。消費型裝置逐 epoch 判定清醒的特異度只有 0.18–0.54(Chinoy 2021;受試者是健康年輕成人、在實驗室裡睡,數字是跨裝置的範圍)。
  2. 切視窗,每段各算一次。 常見的是 5 分鐘一段,也就是 Task Force 1996 建議的短時紀錄長度(1044 頁),每段算一次 RMSSD 與平均心率。整夜一次算出來的 SDNN 已經是另一個量:Day 4 談過,時域指標會隨紀錄長度變大,不同長度量到的 SDNN 不能互比。
  3. 把許多視窗壓成一個數。 可以取平均、取最低、只取某個時段,或依睡眠分期加權。

各家公開說明裡的做法:

廠商 nightly HRV RHR
Oura 睡眠中每 5 分鐘一個樣本,取整夜平均;另外給最大值 夜間每 10 分鐘一個值,同時給整夜平均與最低值
Garmin 睡眠中每 5 分鐘一段,取整段睡眠的平均* 24 小時內最低的 30 分鐘平均*
Polar 入睡後約前 4 小時的 RMSSD 取同一個 4 小時視窗*
WHOOP 睡眠中的加權平均,偏重最後一段慢波睡眠*;自家文件的說法不一致 同 HRV 的加權方式*
Fitbit 過去 24 小時內最長、且超過 3 小時的那段睡眠,算 RMSSD 每天估一次,和夜間的睡眠心率分開;算法沒公開
Apple SDNN,清醒靜止時在背景量,預設每 4 小時一次 排除睡眠時段,估清醒休息時的最低心率

* 依 Dial et al. 2025 對各家文件的整理。其餘取自各家的公開說明與 Apple 2024 年的白皮書。

光指標就不一致:五家用 RMSSD(Oura、Garmin、WHOOP 的部分依 Dial 2025),Apple 用 SDNN,而且 Apple 的 HRV 不是一晚一個數。

2. RHR:同一個名字,三種量法

字面上,RHR 是清醒、靜止時的心率。表裡至少有三種做法:

  • 照字面。 Apple 明文排除睡眠時段,估的是「清醒休息時的最低心率」,每天一個值。Fitbit 也把 RHR 和睡眠心率分開,並寫睡眠心率通常比 RHR 低。
  • 取夜間。 Oura、Polar、WHOOP 從睡眠期間取值。但 Oura 同一晚就給兩個數(平均與最低),Polar 只看前 4 小時。
  • 不限時段。 Garmin 在 24 小時內找最低的 30 分鐘平均,app 不顯示是哪 30 分鐘。Dial 2025 因此對不齊參考值,把它排除在 RHR 的比較之外。

所以「RHR 就是睡覺時的心率」只對一部分裝置成立。夜間取值和臨床說的靜止心率是不同的量測情境;兩者差多少,我沒查到來源,所以夜間 RHR 不能直接拿去對靜止心率的參考區間。

3. HRV:為什麼挑睡眠?挑睡眠的哪一個時段?

挑睡眠有量測上的理由。腕戴與戒指拿到的是 PPG 的脈波間期,嚴格說是 PRV;靜止時 PRV 與 HRV 夠接近,活動時一致性明顯變差(Schäfer & Vagedes 2013,見 Day 4)。睡眠是一天裡最長、條件也最固定的靜止時段。

但睡眠不是均勻的一段。Grosicki & Presby 2025 引用 Trinder 2001 的結果:慢波睡眠的心率比 REM 低 3.5%,標準化高頻 HRV 高 123.7%。所以取整夜、取前 4 小時、偏重慢波睡眠,會得到不同的數字。Polar 的說法是前幾個小時比整夜平均更能反映恢復;WHOOP 偏重最後一段慢波睡眠。兩家都有自己的理由,但都沒有公開到外人能重現的程度。偏重某個睡眠分期還多一層不確定:分期同樣是推估,而且比分辨睡或醒更難,Dial 等人引用的統合分析,四分期的準確度約 60–75%。

常有人說 HR 和 HRV 負相關。這句話要先講清楚是哪一層:跨人比、同一個人跨夜比、同一夜跨視窗比,是三件事,一層的結果不能拿去推另一層。

4. 讀這個數:跟自己比,不是看高低

我查到有公開說明的三家,都是拿當晚的數字跟個人的近期基準比:Oura 拿當晚的最低 RHR 對照過去約 2 個月的平均,Polar 對照過去 28 天,Garmin 累積 3 週建立基準之後,拿 7 日平均去比。廠商呈現的是「偏離自己多少」,不是數值本身的高低。Day 4 已經談過 HRV 為什麼不是越高越好;至於「恢復不佳」「該休息」這類判讀,沒有可對照的參考標準,本系列只當輔助觀察。

Hands-on

Dataset

今天要看的是聚合這一步,所以需要每一段的真值都已知的資料。我用合成的一夜:8 段各自平穩的間期序列接起來,每段內是獨立常態亂數(seed=5),共 28,289 個間期、8.00 小時;各段平均心率 52.1–68.2 bpm,RMSSD 22.7–64.7 ms。每段的平均間期與標準差是我設的,沒有文獻依據,不對應真實的睡眠分期。

Method

  1. 把序列切成 5 分鐘視窗(另外跑 1 分鐘、10 分鐘對照),每窗算 RMSSD、SDNN 與平均心率。不滿一窗的尾段不算,理由在下面。
  2. 把 96 個視窗壓成一個數,三種方法:整夜平均、最低一窗、前 4 小時(0–14,400 秒)的平均。
  3. 聚合一律用平均,跟著廠商走:有公開說明聚合方式的四家寫的都是平均。Oura 是整夜所有 5 分鐘樣本的平均;Garmin 取整段睡眠的平均,Polar 取入睡後約 4 小時的平均,WHOOP 用加權平均(這三家依 Dial 2025 的整理)。Fitbit 與 Apple 沒寫聚合方式,也沒有一家寫中位數。

Code

from wearable_ai.hrv.nightly import nightly_value, window_table
from wearable_ai.synthetic import piecewise_rr

rr, seg = piecewise_rr(NIGHT_SEGMENTS, pattern="jitter", seed=5)   # 8 段,參數見 nightly_windows.py
table = window_table(rr, window_s=300, keep_partial=False)         # 96 個 5 分鐘視窗
nightly_value(table, "rmssd_ms", "mean")                            # 43.82
nightly_value(table, "rmssd_ms", "lowest")                          # 20.61
nightly_value(table, "rmssd_ms", "fixed", period_s=(0, 14_400))     # 52.17

https://ithelp.ithome.com.tw/upload/images/20260918/20184206Y7tPD0tWil.png
上圖是 96 個 5 分鐘視窗的 RMSSD,下圖是心率,兩條折線都在幾個平台之間跳動。三條水平線是三種聚合:前 4 小時平均 52.2 與 56.4、整夜平均 43.8 與 58.9、最低 5 分鐘 20.6 與 52.0;前 4 小時以淺綠底標出

跑出來的數字

96 個 5 分鐘視窗 整夜平均 最低 5 分鐘 前 4 小時平均
RMSSD(ms) 43.82 20.61 52.17
心率(bpm) 58.91 51.96 56.42
  • 同一張視窗表,只換聚合方式,RMSSD 從 20.61 到 52.17 ms,差 2.5 倍。 差距多大取決於我設的參數,方向才是重點:聚合方式一換,數字就換。
  • 兩個「最低」不在同一個時刻。 最低心率那一窗在第 3,900 秒,最低 RMSSD 那一窗在第 25,200 秒。
  • 視窗長度幾乎不影響平均,但會影響最低值。 1、5、10 分鐘視窗的整夜平均是 43.58、43.82、43.86 ms;最低一窗是 18.18、20.61、21.92 ms。
  • 最後 8.9 秒差點成了「最低 5 分鐘」。 8 小時不會剛好切成整數個視窗,最後一窗只有 10 個間期、8.90 秒。留著它,300 秒的 RMSSD 最低值就是它(18.78 ms),整夜中位數也從 43.53 掉到 36.08 ms;整夜平均只動了 0.26 ms。所以尾段不算。

中位數只當對照:

5 分鐘視窗 整夜平均 整夜中位數 前 4 小時平均 前 4 小時中位數
RMSSD(ms) 43.82 43.53 52.17 55.31
心率(bpm) 58.91 58.35 56.42 55.47

整夜兩者差 0.29 ms、0.56 bpm,前 4 小時差 3.14 ms、0.95 bpm。這份資料沒有離群的視窗,所以平均與中位數很接近;真實資料不是這樣,見 Limitations 第 4 條。

結果與意外

原本以為 實際發現
主流廠商的 RHR 多取睡覺時的心率 只對一部分成立。Oura、Polar、WHOOP 取睡眠期間;Apple 早期機型排除睡眠,Fitbit 把 RHR 和睡眠心率分開,Garmin 在 24 小時內找最低的 30 分鐘(Concept 第 2 節)
睡覺時干擾少、心率穩定,所以取這段算 HRV 取睡眠有量測上的理由,但睡眠不是均勻的一段:慢波睡眠的心率比 REM 低 3.5%,標準化高頻 HRV 高 123.7%。而「睡眠」本身是推估出來的(Concept 第 1、3 節)
整夜的數字取中位數 有公開說明聚合方式的廠商都寫平均,沒有一家寫中位數。在合成資料上兩者只差 0.29 ms;nsr001 的 5 分鐘視窗卻差 7.68 ms(平均 32.77、中位數 25.09)
不滿 5 分鐘的最後一段也算(Day 4 的做法) 8.90 秒、10 個間期的尾段成了 RMSSD 最低的一窗(18.78 ms),整夜中位數也從 43.53 掉到 36.08 ms。現在尾段不算

Limitations

  1. 合成的一夜是我設計的。 8 段的參數沒有來源,段與段之間是瞬間跳變,段內是獨立亂數,所以每段的 RMSSD 都約是 SDNN 的 √2 倍(1.38–1.43)。Day 4 的 nsr001 不是這樣:5 分鐘視窗的 RMSSD 中位數只有 SDNN 的一半左右(25.09 對 49.66)。前 4 小時比整夜平均高 8.35 ms,是因為我把高變異的段多放在前 4 小時(前 4 小時裡有 3 小時,後 4 小時只有 1 小時),不是發現。
  2. 管線的第一步沒做。 真實裝置要先從動作與心率推估哪一段是睡眠,這裡直接假設 8 小時全是睡眠、第一拍就是入睡。Polar 的 4 小時從入睡起算,這裡的「前 4 小時」從第一拍起算。
  3. 三種方法只是大致對應廠商。 Oura 的 RHR 是 10 分鐘一段,Garmin 的最低是 24 小時內的 30 分鐘,WHOOP 的加權方式沒公開、無法重現。RMSSD 取最低也沒有廠商這樣做,Oura 給的是最大值。
  4. 平均容易被離群視窗拉動,這份合成資料卻沒有離群視窗。 nsr001 的 270 個 5 分鐘視窗裡有 3 段含訊號遺失,拿掉之後,RMSSD 的視窗平均從 36.46 變 32.77 ms,中位數只從 25.61 變 25.09 ms。用平均是為了跟廠商一致,代價是聚合前得先把壞視窗擋掉,這是 Day 10、11 的事。nsr001 是含白天的 24 小時紀錄,不是一夜。

這對 AI Engineering 的意義

Grosicki & Presby 2025 把「定義對齊」拆成三個維度:指標定義(RMSSD 或 SDNN)、時間視窗(前 4 小時、整夜或特定睡眠分期)、平均或加權方式。只要有一個維度不同,兩個數字就不是同一個量。Dial 2025 讓 13 人戴五款裝置、累積 536 夜,拿去跟 ECG 胸帶比對時,就得逐台處理:Polar 的參考值改成只取前 4 小時;Garmin 的 RHR 因為不知道是哪 30 分鐘而排除;WHOOP 的加權方式無法重現,只能拿整夜平均去比。最後這一點正是那封信的批評。兩位作者受僱於 WHOOP,原文有揭露;Dial 等人回應說,問題出在廠商公開的資訊不足以讓外人對齊。

今天的數字把這三個維度落到一張表上。同一張視窗表,只換聚合方式,RMSSD 就從 20.61 變到 52.17 ms;只換視窗長度,最低一窗從 18.18 變到 21.92 ms;多留一個 8.9 秒的尾段,中位數掉 7.45 ms。這些差異都不會出現在 hrv_nightly_ms = 43.82 這個欄位名裡。所以 nightly metric 交給 LLM 時,metadata 至少要帶:指標(RMSSD 或 SDNN)、時段與它怎麼判定、window_s、聚合方式、尾段怎麼處理、用了幾個視窗。少了這些,模型分不出兩個「HRV」能不能比,例如把 Oura 的整夜平均和 Polar 的前 4 小時放進同一條趨勢線。

還有兩個「最低」。最低心率與最低 RMSSD 來自不同的視窗,除非 feature 帶著時間戳,LLM 不該把它們寫成同一個時刻的事;而 Garmin 的最低 30 分鐘連時間都不給。


上一篇
Day 04|為什麼 HRV 不是越高越好?RMSSD 與 SDNN 在說什麼
下一篇
Day 06|Sampling Rate:取樣頻率為何決定你能相信什麼?
系列文
30 天拆解 Wearable × AI:從穿戴裝置生理訊號到AI健康洞察10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言